package com.gitee.cnzhangjie.excel.convert;

import com.gitee.cnzhangjie.excel.ExcelCell;
import com.gitee.cnzhangjie.excel.ExcelException;
import com.gitee.cnzhangjie.excel.convert.callback.Callback;
import com.gitee.cnzhangjie.excel.convert.callback.ConvertR;
import com.gitee.cnzhangjie.excel.util.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * excel->java bean
 */
public final class ImportConvertExcel extends ConvertExcel {

    private static final Logger logger = LogManager.getLogger(ImportConvertExcel.class);

    private final Workbook workbook;

    private final InputStream inputStream;

    public ImportConvertExcel(
            final ExcelType excelType,
            final InputStream inputStream
    ) throws ExcelException {
        if (excelType == null) {
            throw new NullPointerException("this excelType argument must not be null");
        }
        if (inputStream == null) {
            throw new NullPointerException("this inputStream argument must not be null");
        }
        this.workbook = WorkbookFactory.create(excelType, inputStream);
        this.inputStream = inputStream;
    }

    /**
     * 获得列号和标题的映射
     *
     * @param sheet 当前sheet
     * @return key为行号，value为标题
     */
    private Map<Short, String> getHeadCellMapping(Sheet sheet) {
        Row titleRow = sheet.getRow(sheet.getFirstRowNum());
        if (titleRow == null) {
            return new HashMap<>(0);
        }
        // key为列序，value为列值
        Map<Short, String> result = new HashMap<>();
        for (short i = titleRow.getFirstCellNum(); i <= titleRow.getLastCellNum(); i++) {
            Cell cell = titleRow.getCell(i);
            if (cell == null) {
                continue;
            }
            result.put(i, cell.getStringCellValue());
        }
        return result;
    }

    /**
     * 实体属性与注解的映射
     *
     * @return key为标题，value为字段名
     */
    private <E> Map<String, String> getSheetAnnotationMapping(Class<E> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Map<String, String> map = new HashMap<>(fields.length);
        for (Field field : fields) {
            ExcelCell excelCell = field.getAnnotation(ExcelCell.class);
            if (excelCell != null) {
                map.put(excelCell.value(), field.getName());
            }
        }
        return map;
    }

    /**
     * excel文件转Java Bean
     *
     * @throws ExcelException 转换时的异常
     * @see WorkbookFactory
     */
    public <E> ConvertR read(Class<E> clazz, Callback<E> callback) throws ExcelException {
        String sheetName = getSheetName(clazz);
        Sheet sheet;
        if (this.workbook instanceof SXSSFWorkbook) {
            sheet = ((SXSSFWorkbook) this.workbook).getXSSFWorkbook().getSheet(sheetName);
        } else {
            sheet = this.workbook.getSheet(sheetName);
        }
        if (sheet == null) {
            throw new ExcelException("Sheet name not match, please check class annnotation.");
        }
        Map<Short, String> headCellMapping = this.getHeadCellMapping(sheet);
        Map<String, String> sheetAnnotationMapping = this.getSheetAnnotationMapping(clazz);

        logger.debug("load to excel body.");
        long total = 0L;
        long successTotal = 0L;
        long failTotal = 0L;
        // 遍历数据，排除表头
        for (int i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) {
            total++;
            Row row = sheet.getRow(i);
            if (row == null) {
                failTotal++;
                continue;
            }
            E instance = createBean(row, headCellMapping, sheetAnnotationMapping, clazz);
            if (instance == null) {
                failTotal++;
                continue;
            }
            boolean ok = callback.call(instance);
            if (ok) {
                successTotal++;
            } else {
                failTotal++;
            }
        }
        logger.debug("read ok.");
        return new ConvertR(total, successTotal, failTotal);
    }

    /**
     * 根据row创建一个实例
     *
     * @param row                    excel一行的数据
     * @param headCellMapping        标题映射
     * @param sheetAnnotationMapping sheet注解映射
     * @return 转换成的类
     * @throws ExcelException 反射时创建实体、反射获得/设置字段、单元格获取值失败时的异常
     */
    private <E> E createBean(
            final Row row,
            final Map<Short, String> headCellMapping,
            final Map<String, String> sheetAnnotationMapping,
            final Class<E> clazz
    ) throws ExcelException {
        if (row == null) {
            return null;
        }
        E instance;
        try {
            // 反射创建实体并赋值
            instance = clazz.newInstance();
            for (Map.Entry<Short, String> entry : headCellMapping.entrySet()) {
                // 列号
                Short cellNum = entry.getKey();
                // 通过标题找到该行的列
                Cell cell = row.getCell(cellNum);
                // 当前单元格无值
                if (cell == null) {
                    continue;
                }
                // 列标题
                String cellTitle = entry.getValue();
                // 找到对应标题的java实体属性，并创建实体
                String fName = sheetAnnotationMapping.get(cellTitle);
                if (!StringUtils.isEmpty(fName)) {
                    Field f = clazz.getDeclaredField(fName);
                    f.setAccessible(true);
                    // 根据单元格类型赋值
                    try {
                        f.set(instance, CellType.valueOf(cell, f.getType()));
                    } catch (IllegalStateException e) {
                        throw new ExcelException("Failed to get cell value, msg:" + e.getMessage());
                    }
                }
            }
        } catch (InstantiationException | IllegalAccessException | NoSuchFieldException e) {
            throw new ExcelException("<E> instance create fail or field set error, msg:" + e.getMessage());
        }
        logger.debug("The data with row number " + row.getRowNum() + " was created successfully.");
        return instance;
    }

    @Override
    public void close() throws ExcelException {
        if (this.workbook != null) {
            try {
                inputStream.close();
                this.workbook.close();
            } catch (IOException e) {
                throw new ExcelException("Excel close failure." + e.getMessage());
            }
        }
        logger.debug("Excel closed.");
    }
}
